/*
  Copyright 2006 by Sean Luke and George Mason University
  Licensed under the Academic Free License version 3.0
  See the file "LICENSE" for more information
*/

package sim.util.media.chart;

import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import sim.util.gui.*;

// From JFreeChart (jfreechart.org)
import org.jfree.data.xy.*;
import org.jfree.chart.*;
import org.jfree.chart.event.*;
import org.jfree.chart.plot.*;
import org.jfree.data.general.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.data.general.*;

/** A SeriesAttributes used for user control of histogram series created with HistogramGenerator.
    Unfortunately JFreeChart doesn't have nearly
    as well-designed a histogram chart facility as its time series charts.  There is no HistogramSeries object to
    encapsulate a series, and no facilities for deleting or moving series relative to one another.  
*/

public class HistogramSeriesAttributes extends SeriesAttributes
    {
    double[] values; 
    double[] getValues() { return values; }
    void setValues(double[] vals) { values = vals; }
                
    /** Border thickness */
    float thickness;
    NumberTextField thicknessField;
    /** The color of the histogram bar. */
    Color fillColor;
    ColorWell fillColorWell;
    /** The color of the histogram bar border. */
    Color strokeColor;
    ColorWell strokeColorWell;
    /** The opacity of the histogram bar.  Sadly this must be separate than the color because
        Sun doesn't have a proper color selector.  */
    double fillOpacity;
    NumberTextField fillOpacityField;
        
    /** The opacity of the histogram bar border.  Sadly this must be separate than the color because
        Sun doesn't have a proper color selector.  */
    double lineOpacity;
    NumberTextField lineOpacityField;
    NumberTextField numBinsField;
                
    public void setFillOpacity(double value) { fillOpacityField.setValue(fillOpacityField.newValue(value));  }
    public double getFillOpacity() { return fillOpacityField.getValue(); }
    
    public void setStrokeOpacity(double value) { lineOpacityField.setValue(lineOpacityField.newValue(value));  }
    public double getStrokeOpacity() { return lineOpacityField.getValue(); }

    public void setThickness(double value) { thicknessField.setValue(thicknessField.newValue(value));  }
    public double getThickness() { return (double)(thicknessField.getValue()); }
    
    // we need to store numBins here so update() gets it properly, see later
    int numBins;
    public void setNumBins(int value) { numBinsField.setValue(numBinsField.newValue(value));  numBins = (int)(numBinsField.getValue()); }
    public int getNumBins() { return numBins; }
        
    public void setFillColor(Color value) { fillColorWell.setColor(fillColor = value); }
    public Color getFillColor() { return fillColor; }

    public void setStrokeColor(Color value) { strokeColorWell.setColor(strokeColor = value); }
    public Color getStrokeColor() { return strokeColor; }

    /** Produces a HistogramSeriesAttributes object with the given generator, series name, series index,
        and desire to display margin options. */
    public HistogramSeriesAttributes(ChartGenerator generator, String name, int index, double[] values, int bins, SeriesChangeListener stoppable)  // , boolean includeMargin)
        { 
        super(generator, name, index, stoppable);
        setValues(values);
        super.setName(name);  // just set the name, don't update
        numBins = bins;
        numBinsField.setValue(bins);  // otherwise it'd call newValue(), which would in turn try to update the series, which hasn't been made yet
        numBinsField.setInitialValue(bins);  // make this the default
        }

    /** It's very expensive to call this function (O(n)) because JFreeChart has no way of changing the
        name of a histogram dataset series, and so we must rebuild all of it from scratch. */
    public void setName(String val) 
        {
        super.setName(val); // call this first to set it
        ((HistogramGenerator)generator).update();
        }

    public void rebuildGraphicsDefinitions()
        {
        XYBarRenderer renderer = (XYBarRenderer)getRenderer();
            
        if (thickness == 0.0)
            renderer.setDrawBarOutline(false);
        else
            {
            renderer.setSeriesOutlineStroke(getSeriesIndex(),
                new BasicStroke(thickness));
            renderer.setDrawBarOutline(true);
            }

        renderer.setSeriesPaint(getSeriesIndex(),reviseColor(fillColor, fillOpacity));
        renderer.setSeriesOutlinePaint(getSeriesIndex(),reviseColor(strokeColor, lineOpacity));
        repaint();
        }
        
    static final int DEFAULT_BINS = 8;  // to have enough space  -- it'll get changed soon
    public void buildAttributes()
        {
        // The following three variables aren't defined until AFTER construction if
        // you just define them above.  So we define them below here instead.
        thickness = 2.0f;
        fillOpacity = 1.0;
        lineOpacity = 1.0;

        numBinsField = new NumberTextField("", DEFAULT_BINS ,true)
            {
            public double newValue(double newValue) 
                {
                newValue = (int)newValue;
                if (newValue < 1) 
                    newValue = currentValue;
                                
                // we now set the number of bins directly so update() gets it when it calls getNumBins()
                numBins = (int)newValue;
                ((HistogramGenerator)generator).update();
                return newValue;
                }
            };
        addLabelled("Bins",numBinsField);

        // NOTES:
        // fillColor = (Color)(getRenderer().getSeriesPaint(getSeriesIndex()));
        // this returns null, cause getSeriesPaint returns whatever was set through setSeriesPaint;
        // for the default colors, you need "lookupSeriesPaint()".
        // fillColor = (Color) (getRenderer().lookupSeriesPaint(getSeriesIndex()));
        // getRenderer returns an object implementing the XYItemRenderer interface.
        // either you cast that object to AbstractRenderer, and call lookupSeriesPaint()
        // or you call getItemPaint() on it directly; all getItemPaint does is call lookupSeriesPaint(),
        // but that looks bad, cause getItemPaint() seems to be meant for category data).
        // On the other hand, lookupSeriesPaint() does not show up before 1.0.6, so 
        // in the interest of backward compatibility:
        fillColor = (Color) (getRenderer().getItemPaint(getSeriesIndex(), -1));
        // second argument does not matter

        fillColor = (Color)(getRenderer().getSeriesPaint(getSeriesIndex()));
        fillColorWell = new ColorWell(fillColor)
            {
            public Color changeColor(Color c) 
                {
                fillColor = c;
                rebuildGraphicsDefinitions();
                return c;
                }
            };

        addLabelled("Fill",fillColorWell);

        fillOpacityField = new NumberTextField("Opacity ", fillOpacity,1.0,0.125)
            {
            public double newValue(double newValue) 
                {
                if (newValue < 0.0 || newValue > 1.0) 
                    newValue = currentValue;
                fillOpacity = (float)newValue;
                rebuildGraphicsDefinitions();
                return newValue;
                }
            };
        addLabelled("",fillOpacityField);

        strokeColor = Color.black;
        strokeColorWell = new ColorWell(strokeColor)
            {
            public Color changeColor(Color c) 
                {
                strokeColor = c;
                rebuildGraphicsDefinitions();
                return c;
                }
            };

        addLabelled("Line",strokeColorWell);

        lineOpacityField = new NumberTextField("Opacity ", lineOpacity,1.0,0.125)
            {
            public double newValue(double newValue) 
                {
                if (newValue < 0.0 || newValue > 1.0) 
                    newValue = currentValue;
                lineOpacity = (float)newValue;
                rebuildGraphicsDefinitions();
                return newValue;
                }
            };
        addLabelled("",lineOpacityField);

        thicknessField = new NumberTextField(thickness,false)
            {
            public double newValue(double newValue) 
                {
                if (newValue < 0.0) 
                    newValue = currentValue;
                thickness = (float)newValue;
                rebuildGraphicsDefinitions();
                return newValue;
                }
            };
        addLabelled("Width",thicknessField);
        }
    }
